﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using devtm.Aop.InternalProcess.Cache;
using devtm.Aop.InternalProcess;
using devtm.Aop.Event;
using devtm.Aop.InternalProcess.Lock;

namespace devtm.Aop.Behavior
{


    /// <summary>
    /// Inherit from InterceptMethodAttribute and intercept the entry and exit method event
    /// </summary>
    public class CacheAttribute : InterceptMethodAttribute
    {



        #region private

        private static LockDictionnary cache = new LockDictionnary();
        
        #endregion



        public CacheAttribute() : base()
        {
            Sliding = false;
        }


        /// <summary>
        /// called in begining from the intercepted method
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="devtm.Aop.Event.InterceptEventArgs"/> instance containing the event data.</param>
        public override void EntryMethod(object sender, InterceptEventArgs args)
        {

            CacheItem c;
            ICacheStrategy instance = null;
            uint key;


            Type StrategyType = args.Properties.TryGet("StrategyType") as Type;
            if (StrategyType == null)
                StrategyType = typeof(CacheStrategy);

            instance = cache.GetInstanceStrategyFor<ICacheStrategy>(StrategyType);

            // Est ce que l'on doit le mettre en cache
            if (instance.GetKey(args, out key))
            {

                // On récupère le conteneur
                if (cache.Get(key, out c))
                    
                    // Si on l'a créé on l'initialize
                    instance.InitializeCacheItem(ref c, args);

                args.Tag = c;

                // si c'est un objet NotSettedValue ou si la valeur est expirée on ne recupere pas la valeur du cache
                if (!(c.Value is NotSettedValue) && !c.Time.Expired)
                {

                    args.Return = c.Value;

                    // Permet de breaker la method et de renvoyer la valeur passée dans la propriété args.Return 
                    args.PolicyEntry = PolicyEntryEnum.CancelEntryWithReturnValue;

                    if (c.Time.Sliding)
                        c.Time.Reset();

                }
                else
                {

                    // On passe un par un
                    c.SyncLock.EnterWriteLock();

                    // si c est un objet NotSettedValue ou si la valeur est expirée on ne recupere pas la valeur du cache
                    if (!(c.Value is NotSettedValue) && !c.Time.Expired)
                    {

                        args.Return = c.Value;

                        // Permet de breaker la method et de renvoyer la valeur passée dans la propriété args.Return 
                        args.PolicyEntry = PolicyEntryEnum.CancelEntryWithReturnValue;

                        // Si la valeur est sliding on reset le temps
                        if (c.Time.Sliding)
                            c.Time.Reset();                      

                    }
                    else
                    {
                        args.Return = null;                        
                    }

                }

            }

        }



        /// <summary>
        /// called in end from the intercepted method
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="devtm.Aop.Event.InterceptEventArgs"/> instance containing the event data.</param>
        public override void ExitMethod(object sender, InterceptEventArgs args)
        {
            
            // recupere le CacheItem stoké dans le tag
            CacheItem c = args.Tag as CacheItem;

            if (c != null)
            {
                try
                {
                    if (args.Return != c.Value)
                    {
                        // Si la valeur est différente
                        c.Value = args.Return;
                        c.Time.Reset();
                    }

                }
                finally
                {
                    if (c.SyncLock.IsWriteLockHeld)
                        c.SyncLock.ExitWriteLock();
                }
            }
             
        }


        #region Properties

        public string Key { get; set; }
        public int Time { get; set; }
        public bool Sliding { get; set; }
        public Type StrategyType { get; set; }
        
        #endregion


    }
}
